/******************************************************************************
* This file is part of is32p218 platform,
* Copyright (c) 2009 HangZhou InfoStrong CO.,LTD. All rights reserved.
* This software may only be used under the terms of a valid, current,
* end user license from InfoStrong.
* Nothing else gives you the right to use this software.   
*
* Name:   Internal Flash Test of Erase/Write
* Desc:   
* Author: Jerry
* Date:   2009-9-14
* Note:   
* History:
*
******************************************************************************/
#include "def.h"
#include "option.h"
#include "xn5106x.h"
#include "drv_iflash.h"
#include "sys_log.h"
#include "drv_sys.h"
#include "os_api.h"

#define WR_VERIFY_ON			(0)
#define ERASE_VERIFY_ON			(0)
#define FLASH_SIM				(0)
#define FLASH_TEST				(0)

#define iFLASH_WRITE_KEY()	\
			rFMKEY0 = 0x5a; \
			rFMKEY1 = 0xa5; \
			rFMKEY2 = 0x5a; \
			rFMKEY3 = 0xa5; \

/*
* Function Name:
*   drv_iflash_erase_multi_sector
* Description:
*   iFlash Erase multi-sectors
* Parameters:
*   U32 start: start address of erase area
*  	U32 end: end address of erase area
* Returns:
*   BOOL
* Author				Date 
*  Jerry				2009-9-16   
* Note:
*    
*/
BOOL drv_iflash_erase_multi_sector(U32 start,U32 end)
{
	start &= 0xFFFFF800;
	end &= 0xFFFFF800;

	while (start<=end)
	{
		if (!drv_iflash_erase_sector(start))
		{
			return FALSE;
		}
		start += iFLASH_SECTOR_SIZE;
	}
	return TRUE;
}

/*
* Function Name:
*   drv_iflash_erase_sector
* Description:
*   iFlash Erase one sector
* Parameters:
*   U32 sectorAddr: Address of erase sector( 32bit width )
* Returns:
*   BOOL: TRUE--Success; FALSE--Failure
* Author				Date 
*  Jerry				2009-9-16   
* Note:
*    
*/
BOOL drv_iflash_erase_sector(U32 sectorAddr)
{
#if (FLASH_SIM==0)
#if ERASE_VERIFY_ON
	S32 i;
#endif
	U32 cpu_sr;
#ifdef LOG_FLG
	sys_utils_log( LOG_DRV_IFLASH_ERASE, &sectorAddr, 4 );
#endif
	drv_sys_wdt_feed();

	sectorAddr &= (~(iFLASH_SECTOR_SIZE-1));	//FMADDR[8:0] should be 000000000b
	isOS_ENTER_CRITICAL(cpu_sr);
#ifdef SIMU_UART
	rSYSCON &= (~BIT8);
#endif
	rFMADDR = (sectorAddr>>2);

	iFLASH_WRITE_KEY();
	if(rFMADDR<(FLASH_OPT_START_ADDR>>2)) 
	{
	#ifdef SIMU_UART
		rSYSCON |= (BIT8);
	#endif
		isOS_EXIT_CRITICAL(cpu_sr);
		return FALSE;
	}
#ifdef SIMU_UART
	rSYSCON |= (BIT8);
#endif
	isOS_EXIT_CRITICAL(cpu_sr);
	rFMUCON = iFLASH_OP_PAGE_ERASE;
#if 0
	if (rFMSTA&0x80)
	{
		rFMSTA=0x0;
		//mon_printf("[Iflash_Erasefun_ERR Erase times:%d]\t",i+1);
		if (1==i)
		{
#ifdef LOG_FLG
			sys_utils_log( LOG_DRV_IFLASH_ERASE, "\x00", 1 );
#endif
			return FALSE; //when 2nd failure of program operation.
		}
	}
#endif

#if (ERASE_VERIFY_ON)
	for ( i=0; i< (iFLASH_SECTOR_SIZE); i+=4 )
	{
		U32 tmpWord;
		tmpWord = *((U32 *)(IFLASH_START_ADDR+sectorAddr+i));
		if ( tmpWord!= 0xFFFFFFFF )
		{
#ifdef LOG_FLG
			sys_utils_log( LOG_DRV_IFLASH_ERASE, "\x02", 1 );
#endif
			return FALSE;
		}
	}
#endif

#ifdef LOG_FLG
	sys_utils_log( LOG_DRV_IFLASH_ERASE, "\x01", 1 );
#endif

#else		// Simulate
	U32 i;
	U8 *p_flash_addr;
	p_flash_addr = (U8*)sectorAddr;
	i = iFLASH_SECTOR_SIZE;
	while (i--)
	{
		*p_flash_addr++ = 0xFF;
	}
#endif
	rFMADDR = 0x1FFF;
	return TRUE;
}

/*
* Function Name:
*   drv_iflash_write_word
* Description:
*   iFlash Write word data
* Parameters:
*   U32 addr: Address( should align word)
*   U32 data: Data
* Returns:
*   BOOL: TRUE--Success; FALSE--Failure
* Author				Date 
*  Jerry				2009-9-16   
* Note:
*    
*/
BOOL drv_iflash_write_word(U32 addr,U32 data)
{
#if (FLASH_SIM==0)
	U32 cpu_sr;

	isOS_ENTER_CRITICAL(cpu_sr);
#ifdef SIMU_UART
	rSYSCON &= (~BIT8);
#endif

	addr &= 0xFFFFFFFC;
	rFMADDR = (addr>>2);		// Word mode address
	rFMDATA = data;

	iFLASH_WRITE_KEY();
	if(rFMADDR<(FLASH_OPT_START_ADDR>>2)) 
	{
	#ifdef SIMU_UART
		rSYSCON |= (BIT8);
	#endif
		isOS_EXIT_CRITICAL(cpu_sr);
		return FALSE;
	}
	rFMUCON = iFLASH_OP_WORD_PROGRAM;

#ifdef SIMU_UART
	rSYSCON |= (BIT8);
#endif
	isOS_EXIT_CRITICAL(cpu_sr);
#if 0
	if (rFMSTA&0x80)
	{
		rFMSTA=0x0;
		if (i==1)
		{
#ifdef LOG_FLG
			sys_utils_log( LOG_DRV_IFLASH_WRITE, "\x00", 1 );
#endif
			return FALSE; //when 2nd failure of program operation.
		}
	}
#endif

#if (WR_VERIFY_ON)
	{
		U32 tmpWord;
		tmpWord = *((U32 *)(IFLASH_START_ADDR+addr));
		if ( tmpWord!=data )
		{
			return FALSE;
		}
	}
#endif

#else	// Simulate
	*((U32*)addr) = data;
#endif
	rFMADDR = 0x1FFF;
	rFMDATA = 0xFFFFFFFF;
	
	return TRUE;
}

/*
* Function Name:
*   drv_iflash_read_data
* Description:
*   iFlash read data
* Parameters:
*   U32 addr: start address
*   U8* p_data: data pointer
*   U32 data_len: data length of reading
* Returns:
*   BOOL:
* Author				Date 
*  Jerry				2010-3-3   
* Note:
*    
*/
BOOL drv_iflash_read_data( U32 addr, U8 *p_data, U32 data_len )
{
	U8 *p_flash_addr;
	if ( addr < FLASH_DATA_ADDR \
		 ||(addr+data_len)>=(FLASH_DATA_ADDR+FLASH_DATA_SIZE) )
	{
		return FALSE;
	}

	p_flash_addr = (U8*)addr;
	while( data_len-- )
	{
		*p_data++ = *p_flash_addr++;
	}

	return TRUE;
}

/*
* Function Name:
*   drv_iflash_write_data
* Description:
*   Write data to flash
* Parameters:
*   U32 addr:
*   U8* p_data:
*   U32 data_len:
* Returns:
*   BOOL:
* Author				Date 
*  Jerry				2009-12-7   
* Note:
*    
*/
BOOL drv_iflash_write_data( U32 addr, const U8 *p_data, U32 data_len )
{
	U32 data_tmp;
	U32 len_tmp;

	IS_ASSERT( p_data!=NULL && data_len>0 );

	if ( (NULL==p_data)||(0==data_len) )
	{
		return FALSE;
	}

	if ( addr < FLASH_DATA_ADDR \
		 ||(addr+data_len)>=(FLASH_DATA_ADDR+FLASH_DATA_SIZE) )
	{
		return FALSE;
	}

#ifdef LOG_FLG
	sys_utils_log( LOG_DRV_IFLASH_WRITE, &addr, 4 );
	sys_utils_log( LOG_DRV_IFLASH_WRITE, (U8*)p_data, data_len );
#endif
	drv_sys_wdt_feed();

	// ----- Pre-write ----
	len_tmp = addr&0x3;
	if ( len_tmp )
	{	// Not align
		U8 *p_tmp_data;

		data_tmp = *((U32*)(addr&0xFFFFFFFC));
		p_tmp_data = (U8*)&data_tmp +len_tmp;

		len_tmp = sizeof(U32)-len_tmp;
		if(data_len < len_tmp)
		{
			len_tmp = data_len;
			data_len = 0;
		}
		else
		{
			data_len -= len_tmp;
		}
		while (len_tmp--)
		{
			*p_tmp_data++ = *p_data++;
		}

		if ( FALSE == drv_iflash_write_word( addr, data_tmp ) )
		{
		#ifdef LOG_FLG
			sys_utils_log( LOG_DRV_IFLASH_WRITE, "\x02", 1 );
		#endif
			return FALSE;
		}
		addr = (addr+3)&0xFFFFFFFC;
	}
	// ----- Align-write ----
	while (data_len>=4)
	{
		data_tmp = *p_data++;
		data_tmp += (*p_data++<<8);
		data_tmp += (*p_data++<<16);
		data_tmp += (*p_data++<<24);
		if ( FALSE == drv_iflash_write_word( addr, data_tmp ) )
		{
		#ifdef LOG_FLG
			sys_utils_log( LOG_DRV_IFLASH_WRITE, "\x03", 1 );
		#endif
			return FALSE;
		}
		data_len -= 4;
		addr += 4;
	}
	// ----- Post-write ----
	if ( data_len )
	{	// Not align
		U8 *p_tmp_data;

		data_tmp = *((U32*)(addr&0xFFFFFFFC));
		p_tmp_data = (U8*)&data_tmp;

		while (data_len--)
		{
			*p_tmp_data++ = *p_data++;
		}

		if ( FALSE == drv_iflash_write_word( addr, data_tmp ) )
		{
		#ifdef LOG_FLG
			sys_utils_log( LOG_DRV_IFLASH_WRITE, "\x04", 1 );
		#endif
			return FALSE;
		}
	}

#ifdef LOG_FLG
	sys_utils_log( LOG_DRV_IFLASH_WRITE, "\x01", 1 );
#endif

	return TRUE;
}


#if FLASH_TEST

/*
* Function Name:
*   drv_iflash_test
* Description:
*   Flash Test
* Parameters:
* Returns:
* Author				Date 
*  Jerry				2009-12-31   
* Note:
*    
*/
void drv_iflash_test( void )
{
	U32 test_addr = 0x2F000;
	BOOL result = TRUE;

	result = drv_iflash_erase_sector( test_addr );

	result = drv_iflash_write_data( test_addr+0x1, (U8*)"\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB", \
						   sizeof("\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB")-1 );

	result = drv_iflash_write_data( test_addr+0x22, (U8*)"\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB", \
						   sizeof("\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB")-1 );

	result = drv_iflash_write_data( test_addr+0x43, (U8*)"\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB", \
						   sizeof("\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB")-1 );

	result = drv_iflash_write_data( test_addr+0x60, (U8*)"\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB", \
						   sizeof("\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB")-1 );
}

#endif

/*
* Function Name:
*   drv_iflash_init
* Description:
*   iFlash init
* Parameters: 
*   None 
* Returns: 
*   None 
* Author				Date 
*  Jerry				2009-9-16   
* Note:
*    
*/
void drv_iflash_init( void )
{
	rFMCN = (EXTCLK_FREQ/1000000);
#if FLASH_TEST
	drv_iflash_test();
#endif
}

/*
* Function Name:
*   drv_iflash_check
* Description:
*   iFlash check
* Parameters: 
*   None 
* Returns: 
*   None 
* Author				Date 
*  feye				    2010-6-2   
* Note:
*    
*/
BOOL drv_iflash_check( void )
{
	 if((EXTCLK_FREQ/1000000)!=rFMCN)
	 {
	 	return FALSE;
	 }
	 return TRUE;
}

